package com.lagou.sql;

import com.lagou.pojo.Configuration;
import com.lagou.pojo.MappedStatement;
import com.lagou.utils.GenericTokenParser;
import com.lagou.utils.ParameterMapping;
import com.lagou.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Kirito
 * @description:
 * @date 2020/03/25
 */
public class SimpleExecutor implements Executor {

    private Connection connection;

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement statement, Object... params) throws Exception {
        PreparedStatement preparedStatement = getPreparedStatement(configuration, statement, params);
        // 5.执行sql
        ResultSet resultSet = preparedStatement.executeQuery();

        // 6.结果集处理，将resultSet中的数据封装成返回值对象
        // 获取返回值的全限定名："com.lagou.pojo.User"
        String resultType = statement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);

        ArrayList<Object> objects = new ArrayList<>();
        while (resultSet.next()) {
            Object obj = resultTypeClass.newInstance();
            // 获取每一条结果集的元数据，元数据包含列名及其对应值
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                String columnName = metaData.getColumnName(i);
                // 根据列名从结果集中获取对应值
                Object columnValue = resultSet.getObject(columnName);
                // 获取到列名后需要将列值设置到obj对象对应的属性上，通过反射/内省来完成
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(obj, columnValue);
            }
            objects.add(obj);
        }
        return (List<E>) objects;
    }

    @Override
    public void update(Configuration configuration, MappedStatement statement, Object... params) throws Exception {
        PreparedStatement preparedStatement = getPreparedStatement(configuration, statement, params);
        preparedStatement.executeUpdate();
    }

    /**
     * 获取预处理对象
     *
     * @param configuration Configuration
     * @param statement     MappedStatement
     * @param params        Object
     * @return PreparedStatement
     * @throws Exception Exception
     */
    private PreparedStatement getPreparedStatement(Configuration configuration, MappedStatement statement, Object... params) throws Exception {
        // 1.直接从连接池中获取连接
        connection = configuration.getDataSource().getConnection();

        // 2.获取sql语句：select * from user where id=#{id} and username=#{username}
        // 需要替换占位符，将#{}替换成?，获取#{}里面的参数名称并进行存储
        BoundSql boundSql = getBoundSql(statement.getSql());

        // 3.预处理
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4.参数设置
        Class<?> parameterTypeClass = getClassType(statement.getParameterType());
        if (null == parameterTypeClass) {
            // 没有参数的直接返回
            return preparedStatement;
        }
        boolean baseType = isBaseType(parameterTypeClass);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        for (int i = 0; i < parameterMappings.size(); i++) {
            Object value = null;
            if (baseType) {
                // 基本数据类型直接设置参数
                value = params[i];
            } else {
                // 对象类型参数通过反射赋值
                ParameterMapping parameterMapping = parameterMappings.get(i);
                String parameterName = parameterMapping.getContent();
                Object parameterObject = getParameterObject(parameterName, params);
                if (null == parameterObject) {
                    throw new RuntimeException("sql占位符参数不属于任何对象");
                }
                // 反射获取参数对应的字段
                Field field = parameterTypeClass.getDeclaredField(parameterName);
                field.setAccessible(true);
                // 获取字段对应的值
                value = field.get(parameterObject);
            }
            preparedStatement.setObject(i + 1, value);
        }
        return preparedStatement;
    }

    @Override
    public void close() throws SQLException {
        connection.close();
    }

    private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
        if (null != parameterType) {
            return Class.forName(parameterType);
        }
        return null;
    }

    /**
     * 完成对#{}的解析工作：1.将#{}使用?代替，2.解析出#{}的参数名称进行存储
     *
     * @param sql String
     * @return BoundSql
     */
    private BoundSql getBoundSql(String sql) {
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser tokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        String parseSql = tokenParser.parse(sql);
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        return new BoundSql(parseSql, parameterMappings);
    }

    /**
     * 判断参数类型是否是基本数据类型
     *
     * @param clazz Class
     * @return boolean
     */
    private boolean isBaseType(Class<?> clazz) {
        if (null == clazz) {
            throw new RuntimeException("参数类型错误");
        }
        return clazz.equals(Integer.class) ||
                clazz.equals(int.class) ||
                clazz.equals(Byte.class) ||
                clazz.equals(byte.class) ||
                clazz.equals(Long.class) ||
                clazz.equals(long.class) ||
                clazz.equals(Double.class) ||
                clazz.equals(double.class) ||
                clazz.equals(Float.class) ||
                clazz.equals(float.class) ||
                clazz.equals(Character.class) ||
                clazz.equals(char.class) ||
                clazz.equals(Short.class) ||
                clazz.equals(short.class) ||
                clazz.equals(Boolean.class) ||
                clazz.equals(boolean.class);
    }

    /**
     * getParameterObject
     *
     * @param name   String
     * @param params Object
     * @return Object
     */
    private Object getParameterObject(String name, Object[] params) {
        for (Object param : params) {
            Field[] declaredFields = param.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                String fieldName = declaredField.getName();
                if (name.equalsIgnoreCase(fieldName)) {
                    return param;
                }
            }
        }
        return null;
    }
}
